/*
    This file is part of QTau
    Copyright (C) 2013-2018  Tobias "Tomoko" Platen <tplaten@posteo.de>
    Copyright (C) 2013       digited       <https://github.com/digited>
    Copyright (C) 2010-2013  HAL@ShurabaP  <https://github.com/haruneko>

    QTau is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

    SPDX-License-Identifier: GPL-3.0+
*/

// [a] from Hatsune Miku, estimated using praat
// used to configure EpR voice model, see http://www.mtg.upf.edu/node/1231

float init_f[6] = {915.597,1408.48,1892.801,4025.529,4509.241,5500};
float init_bw[6]      = {174.803,167.995,125.245 ,411.338 ,429.417, 0};
float init_gain_db[6] = {15,15,10,5,5,2};

float init_controls[3] = { 22.5, -7E-5, 77  };


#include <sekai/midi.h>

/* --------------------------------------------------------------------------- *
 *
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
* --------------------------------------------------------------------------- */ 

#define __devloglevel__ 20

#include "madde_synth.h"
#include <unistd.h>
#include <QAction>
#include <QTimer>
#include "maddefile.h"
#include <QtConcurrent/QtConcurrent>

QString MaddeLOIDSynth::name()        { return "MaddeLOID"; }
QString MaddeLOIDSynth::description() { return "An additive singing synthesizer"; }
QString MaddeLOIDSynth::version()     { return "18.04"; }

#include "maddewindow.h"
#include <math.h>

void MaddeLOIDSynth::start(float keyNum)
{
    if(_threadRunning) return;

    _f0 = frequencyFromNote(keyNum);
    _phase = 0;

   // _ctrl->startThread(this);
    QtConcurrent::run(this,&MaddeLOIDSynth::run);


}
void MaddeLOIDSynth::stop()
{
    _f0 = 0;
}

int MaddeLOIDSynth::run()
{
    _threadRunning=true;
    while(_f0) {
        while(jack_ringbuffer_write_space(_ringbuffer)<FFT_SIZE/2*sizeof(float)) {
            usleep(1000);
            if(_f0==0) break;
        }
        if(_f0){
            int nharmonics = 50;

            for(int i=0;i<6;i++)
            {
                if(_resNeedsUpdate[i])
                {
                    EprResonanceUpdate(&_res[i],_osc.fs);
                    _resNeedsUpdate[i]=false;
                }
            }

            for(int i=0;i<50;i++)
            {
                if(_f0>0) {
                    float factor = 0.1;
                    float f = _f0*(i+1);
                    double gain = EprAtFrequency(&_src,f,_osc.fs,_res,RES_COUNT);
                    _osc.amp[i] = pow(M_E,(gain/TWENTY_OVER_LOG10))/nharmonics*factor;
                    _osc.frq[i] = f;
                }
                else {
                    _osc.amp[i]=0;
                    _osc.frq[i]=0;
                }

            }

            _osc.processOneFrame();


            float output_buffer[FFT_SIZE/2];
            for(int i=0;i<FFT_SIZE/2;i++)
            {
                output_buffer[i] = _osc.output_buffer[i]*0.9;
            }
            jack_ringbuffer_write(_ringbuffer,(char*)output_buffer,FFT_SIZE/2*sizeof(float));
        };
    }
    _threadRunning=false;
    return 0;
}

void MaddeLOIDSynth::readData(float* data,int dataLength)
{
   unsigned int dataLength2 = dataLength*sizeof(float);
   unsigned int readSpace = jack_ringbuffer_read_space(_ringbuffer);

   readSpace = jack_ringbuffer_read_space(_ringbuffer);

   if(readSpace>dataLength2)
      jack_ringbuffer_read(_ringbuffer,(char*)data,dataLength2);

}

void MaddeLOIDSynth::setup2() //rename to setupUI
{
    QAction* madde = new QAction("MaddeLOID",this);
    _ctrl->addPluginAction(madde);
    connect(madde,SIGNAL(triggered()),this,SLOT(madde_triggered()));
}

void MaddeLOIDSynth::madde_triggered()
{
    if(_madde) return;

    _madde = new MaddeWindow();
    _madde->setVisible(true);

    connect(_madde,SIGNAL(loadDefault()),this,SLOT(loadDefault()));
    connect(_madde,SIGNAL(loadFile(QString)),this,SLOT(loadFile(QString)));
    connect(_madde,SIGNAL(saveToFile(QString)),this,SLOT(saveToFile(QString)));
    connect(_madde,SIGNAL(mouseEvent(int)),this,SLOT(mouseEvent(int)));

    connect(_madde,SIGNAL(valueChanged(QString,QVariant)),this,SLOT(madde_valueChanged(QString,QVariant)));
    connect(_madde,SIGNAL(closed()),this,SLOT(madde_closed()));

    for(int i=0;i<6;i++)
    {
        _madde->setValue("F"+QVariant(i+1).toString(),init_f[i]);
        _madde->setValue("BW"+QVariant(i+1).toString(),init_bw[i]);
        _madde->setValue("A"+QVariant(i+1).toString(),init_gain_db[i]);
        if(i!=5) {
            _madde->setValue("UseF"+QVariant(i+1).toString(),true);
            _resEnabled[i]=true;
        } else
            _resEnabled[i]=false;
        _resNeedsUpdate[i]=false;
    }

    _madde->setValue("gaindb",init_controls[0]);
    _madde->setValue("slope",init_controls[1]);
    _madde->setValue("slopedepthdb",init_controls[2]);
}

void MaddeLOIDSynth::loadDefault()
{
    for(int i=0;i<6;i++)
    {
        _madde->setValue("F"+QVariant(i+1).toString(),init_f[i]);
        _madde->setValue("BW"+QVariant(i+1).toString(),init_bw[i]);
        _madde->setValue("A"+QVariant(i+1).toString(),init_gain_db[i]);
        if(i!=5) {
            _madde->setValue("UseF"+QVariant(i+1).toString(),true);
            _resEnabled[i]=true;
        } else
            _resEnabled[i]=false;
        _resNeedsUpdate[i]=false;
    }

    //synth
    for(int i=0;i<RES_COUNT;i++)
    {
        _res[i].f = init_f[i];
        _res[i].bw = init_bw[i];
        _res[i].gain_db = init_gain_db[i];
        _res[i].enabled=1;
        EprResonanceUpdate(&_res[i],_osc.fs);
    }
    _res[5].enabled=0;

    _src.gaindb = init_controls[0];
    _src.slope = init_controls[1];
    _src.slopedepthdb = init_controls[2];

    _madde->setValue("gaindb",init_controls[0]);
    _madde->setValue("slope",init_controls[1]);
    _madde->setValue("slopedepthdb",init_controls[2]);
}

void MaddeLOIDSynth::loadFile(QString fileName)
{
   MaddeFile file;
   file.readFromFile(fileName);

   _src.gaindb = file.getEpRSource("gaindb").toDouble();
   _src.slopedepthdb = file.getEpRSource("slopedepthdb").toDouble();
   _src.slope = file.getEpRSource("slope").toDouble();
   _madde->setValue("gaindb",_src.gaindb);
   _madde->setValue("slopedepthdb",_src.slopedepthdb);
   _madde->setValue("slope",_src.slope);

   for(int i=0;i<6;i++)
   {
       float frq = file.getFormant("F"+QVariant(i+1).toString()).toFloat();
       float q = file.getFormant("Q"+QVariant(i+1).toString()).toFloat();
       QVariant a = file.getFormant("A"+QVariant(i+1).toString());
       bool useformant = file.getFormant("UseF"+QVariant(i+1).toString()).toBool();
       float a2=init_gain_db[i];
       if(a.toString().length()>0) a2=a.toFloat();
       _madde->setValue("F"+QVariant(i+1).toString(),frq);
       _madde->setValue("BW"+QVariant(i+1).toString(),frq/q);
       _madde->setValue("A"+QVariant(i+1).toString(),a2);
       if(useformant) {
           _madde->setValue("UseF"+QVariant(i+1).toString(),true);
           _resEnabled[i]=true;
       } else
       {
           _resEnabled[i]=false;
           _madde->setValue("UseF"+QVariant(i+1).toString(),false);
       }

       _res[i].f = frq;
       _res[i].bw = frq/q;
       _res[i].gain_db = a2;
       _res[i].enabled=useformant;
       EprResonanceUpdate(&_res[i],_osc.fs);

   }
}

void MaddeLOIDSynth::madde_valueChanged(QString key,QVariant value)
{
    if(key[0]=='F')
    {
        key.replace("F","");
        int index = QVariant(key).toInt()-1;
        _res[index].f=value.toFloat();
        _resNeedsUpdate[index]=true;
    }
    if(key[0]=='A')
    {
        key.replace("A","");
        int index = QVariant(key).toInt()-1;
        _res[index].gain_db=value.toFloat();
        _resNeedsUpdate[index]=true;
    }
    if(key[0]=='Q')
    {
        key.replace("Q","");
        int index = QVariant(key).toInt()-1;
        _res[index].bw=_res[index].f/value.toFloat();
        _resNeedsUpdate[index]=true;
    }
    if(key.startsWith("UseF"))
    {
        key.replace("UseF","");
        int index = QVariant(key).toInt()-1;
        _res[index].enabled=(int)value.toBool();
    }
    if(key=="gaindb") _src.gaindb=value.toFloat();
    if(key=="slope") _src.slope=value.toFloat();
    if(key=="slopedepthdb") _src.slopedepthdb=value.toFloat();


}

void MaddeLOIDSynth::madde_closed()
{
    _madde->deleteLater();
    _madde = nullptr;
}

void MaddeLOIDSynth::setup(IController *ctrl) {

        _ctrl = ctrl;

        _osc.fs = ctrl->sampleRate();

        QTimer::singleShot(100, this,SLOT(setup2()));

        //load default values

        for(int i=0;i<RES_COUNT;i++)
        {
            _res[i].f = init_f[i];
            _res[i].bw = init_bw[i];
            _res[i].gain_db = init_gain_db[i];
            _res[i].enabled=1;
            EprResonanceUpdate(&_res[i],_osc.fs);
        }
        _res[5].enabled=0;

        _src.gaindb = init_controls[0];
        _src.slope = init_controls[1];
        _src.slopedepthdb = init_controls[2];

        _ringbuffer = jack_ringbuffer_create(4096*sizeof(float));


}

void MaddeLOIDSynth::saveToFile(QString fileName)
{
    MaddeFile file;

    for(int i=0;i<6;i++)
    {
        float f = _res[i].f;
        if(f==0) f=init_f[i];
        float q = f/_res[i].bw;
        if(std::isnan(q)) q = 10;

        file.setFormant("F"+QVariant(i+1).toString(),f);
        file.setFormant("Q"+QVariant(i+1).toString(),q);
        file.setFormant("A"+QVariant(i+1).toString(),_res[i].gain_db);
        file.setFormant("UseF"+QVariant(i+1).toString(),_res[i].enabled);
    }

    file.setEpRSource("gaindb",_src.gaindb);
    file.setEpRSource("slope",_src.slope);
    file.setEpRSource("slopedepthdb",_src.slopedepthdb);

    file.writeToFile(fileName);
}

void MaddeLOIDSynth::mouseEvent(int status)
{
    DEVLOG_INFO("mouse "+STR(status))
    if(status==1)
        start(48);
    else
        stop();
}
